import { omit } from 'lodash-es'
import { ref, watch, defineEmits, type Ref, type PropType } from 'vue'
import Lan from '../lang/zh'
import { ElMessage, dayjs } from 'element-plus'
import BigNumber from 'bignumber.js'
import SparkMD5 from 'spark-md5'
import axios from 'axios'

export interface SimpleKeyArrayObject {
  [key: string]: string | number
}
export interface SimpleKeyValueObject {
  [key: string]: BaseType
}
export type BaseType =
  | string
  | number
  | object
  | boolean
  | undefined
  | null
  | symbol
  | Array<string>
  | Array<SimpleKeyValueObject>
  | SimpleKeyValueObject
export interface iFormDataProp {
  formData: SimpleKeyValueObject
  prop: string
}
export const formDataProp = {
  formData: {
    type: Object
  },
  prop: {
    type: String
  }
}
/**
 * 剔除自定义属性，只保留el原始属性
 * @param props
 * @param selfProps
 * @returns
 */
export function omitSelfProps<T extends Object, M extends Object>(
  props: T,
  selfProps: M
) {
  return omit(props, Object.keys(selfProps))
}

// export function makeValue(initValue: Array<String>): Ref<Array<String>>
// export function makeValue(initValue: String): Ref<String>
export function makeValue(
  emit: Function,
  formData: Object | undefined,
  prop: string = ''
): Ref<BaseType> {
  const initVal = (formData as SimpleKeyValueObject)[prop]
  //   const emit = defineEmits(['update:formdata'])
  const val = ref(initVal)
  watch(val, () => {
    ;(formData as SimpleKeyValueObject)[prop] = val.value
    emit('update:formdata', val)
  })
  return val
}
export const definePropType = <T>(val: any): PropType<T> => val

export const $AONE_LAN = Lan

export const getValOrFnResult = (fn: Function | BaseType | JSX.Element) =>
  typeof fn == 'function' ? fn() : fn

export const assert = (condition: boolean, msg: string) => {
  if (!condition) {
    throw new Error(msg)
  }
}

export const IgnoreProps = {
  label: {},
  value: {},
  inline: Boolean,
  style: String,
  slots: {}
}

export function copyText(text: string) {
  try {
    var textareaC = document.createElement('textarea')
    textareaC.setAttribute('readonly', 'readonly') //设置只读属性防止手机上弹出软键盘
    textareaC.value = text
    document.body.appendChild(textareaC) //将textarea添加为body子元素
    textareaC.select()
    var res = document.execCommand('copy')
    document.body.removeChild(textareaC) //移除DOM元素
    ElMessage.success($AONE_LAN.copySuccess)
  } catch (e) {
    ElMessage.error($AONE_LAN.copyFail)
  }
}

export const toNumber = (val: string | number, per: number = 0) => {
  return BigNumber(val).toFixed(per)
}

export const toTime = (val: number, format: string = 'YYYY-MM-DD HH:mm:ss') => {
  return dayjs(val).format(format)
}

export const searchParentByClassName = (
  dom: HTMLElement | null,
  className: string
): HTMLElement | null => {
  if (dom && Array.from(dom.classList).includes(className)) {
    return dom
  }
  return dom ? searchParentByClassName(dom.parentElement, className) : null
}

export class SchedulePromise<T> {
  waitQueue = [] as Function[]
  runQueue = [] as Function[]
  limit = 1
  callback: Function | null = null
  constructor(callback: Function, limit: number = 3) {
    this.limit = limit
    this.callback = callback
  }
  push(ps: Function[]) {
    if (Array.isArray(ps)) {
      this.waitQueue.push(...ps)
    } else {
      this.waitQueue.push(ps)
    }
    this.run()
  }
  run() {
    while (this.waitQueue.length > 0 && this.runQueue.length < this.limit) {
      const ps = this.waitQueue.shift()
      if (ps) {
        new Promise(resolve => {
          ps(resolve)
        }).then(() => {
          this.runQueue = this.runQueue.filter(it => it != ps)
          this.run()
        })
        this.runQueue.push(ps)
      }
    }
    if (this.waitQueue.length == 0 && this.runQueue.length == 0) {
      this.callback?.()
    }
  }
}

export const getFileMd5 = async (
  file: File,
  chunkSize: number = 5 * 1024 * 1024
) => {
  const spark = new SparkMD5.ArrayBuffer(),
    chunkCount = Math.ceil(file.size / chunkSize),
    promiseList = []
  for (let i = 0; i < chunkCount; i++) {
    const promise = new Promise(resolve => {
      const blob = file.slice(i * chunkSize, (i + 1) * chunkSize)
      const fileReader = new FileReader()
      fileReader.readAsArrayBuffer(blob)
      fileReader.onload = function (e) {
        spark.append(e!.target!.result as ArrayBuffer)
        resolve(1)
      }
    })
    promiseList.push(promise)
  }
  await Promise.all(promiseList)
  return spark.end()
}

export const checkServerFileMD5 = (
  url: string,
  fileName: string,
  fileMd5Value: string
) => {
  return axios.get(`${url}?fileName=${fileName}&fileMd5Value=${fileMd5Value}`)
}

export const checkAndUploadChunk = (
  file: File,
  chunkSize: number,
  fileMd5Value: string,
  chunkList: string[]
) => {
  const chunkCount = Math.ceil(file.size / chunkSize),
    formDataList: FormData[] = []
  for (let i = 0; i < chunkCount; i++) {
    if (!chunkList.includes(i.toString())) {
      let form = new FormData()
      form.append('data', file.slice(i * chunkSize, i * chunkSize + chunkSize)) //  // 当前片数据
      form.append('total', chunkCount.toString()) //总片数
      form.append('index', i.toString()) //当前是第几片
      form.append('fileMd5Value', fileMd5Value)

      formDataList.push(form)
    }
  }
  return formDataList
}

export const bigFileUploader = async (
  file: File,
  callback?: Function,
  chunkSize: number = 5 * 1024 * 1024
) => {
  // 1.校验文件，返回md5
  const fileMd5Value = await getFileMd5(file)

  const BaseUrl = 'http://localhost:1111'
  // 2.校验文件的md5
  const { data } = await axios.get(
    BaseUrl +
      `/api/check/file?fileName=${file.name}&fileMd5Value=${fileMd5Value}`
  )

  if (data?.file) {
    console.log('文件已秒传')
    return
  }
  const existChunkList = data.chunkList

  // 3.按个检查断点续传
  const formDataList = checkAndUploadChunk(
    file,
    chunkSize,
    fileMd5Value,
    existChunkList
  ) as FormData[]

  await new Promise(resolve => {
    new SchedulePromise(resolve).push(
      formDataList.map(form => {
        return (resolve: Function) =>
          axios({
            method: 'post',
            url: BaseUrl + '/api/upload',
            data: form
          }).then(({ data }) => {
            if (data.stat) {
              existChunkList.push(form.get('index'))
              // 已上传片数，总片数
              callback?.(
                Number(existChunkList.length),
                Number(form.get('total'))
              )
              resolve(1)
            }
          })
      })
    )
  })

  // 4：通知服务器所有服务器分片已经上传完成
  axios
    .get(
      BaseUrl +
        '/api/merge?md5=' +
        fileMd5Value +
        '&fileName=' +
        file.name +
        '&size=' +
        file.size
    )
    .then(({ data }) => {
      if (data.stat) {
        console.log('上传成功')
      } else {
        console.error('上传失败')
      }
    })
}

export const downloadBlob = (blob: Blob, filename: string) => {
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = filename
  a.click()
  window.URL.revokeObjectURL(url)
}
